/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import java.util.Random;
import java.awt.Color;
 
public class MonteCarloAI implements Player
{
	private static final int DEFAULT_TIMESETTING = 500; //default is 500 iterations
	private static final int DEFAULT_FORCEMODE = 100;
	private int timesetting;
	private int forcemode;
	public int numCollisions;
	private Table table;
	private Random generator;
	private boolean viewShots; // to show how he is taking test-shots
	private Display display; // needed to show those test-shots
	private String name;
	private boolean firstShot;
	
	/**
		Creates an instance of the AI
		@param i an int value that decides how many random-samples to take  
		@param j an int value for the amount of force that is added to each shot (which part of the 500 is fixed, tweaking param)
		@param g The Table to play on
		@param d The display (optional but then you shouldn't try to print the testing-shots
		@param name The name of this AI
	*/
	public MonteCarloAI(int i,int j, Table g, Display d, String name)
	{
		timesetting = i;
		forcemode = j;
		table = g;
		display = d;
		generator = new Random();
		viewShots = false;
		this.name = name;
		firstShot = true;
	} 
	
	/**
		Creates an instance of the AI
		@param i an int value that decides how many random-samples to take 
		@param g The Table to play on
		@param d The display (optional but then you shouldn't try to print the testing-shots
		@param name The name of this AI
	*/
	public MonteCarloAI(int i, Table g, Display d, String name)
	{
		timesetting = i;
		forcemode = DEFAULT_FORCEMODE;
		table = g;
		display = d;
		generator = new Random();
		viewShots = false;
		this.name = name;
		firstShot = true;
	}
	
	/**
		Creates an instance of the Ai with the default setting for the amount of samples
		@param g The Table to play on
		@param d The display (optional but then you shouldn't try to print the testing-shots
		@param name The name of this AI
	*/
	public MonteCarloAI(Table g, Display d, String name)
	{
		timesetting = DEFAULT_TIMESETTING;
		forcemode = DEFAULT_FORCEMODE;
		table = g;
		display = d;
		generator = new Random();
		viewShots = false;
		this.name = name;
		firstShot = true;
	}
	
	/**
		Returns the next shot that the AI will be making
		@return Cue object with the parameters set for the next shot
	*/
	public Cue getShot()
	{
		Table AITable; 
		Cue shooter; 
		int points;
		Simulation sim;
		Cue bestShot = null; // the best of the best
		int best = -100; // set to something really low that can't be reached, how horrible a ball is played
		
		Ball cueBall = null; // the search for the cueBall
		for (Ball b : table.getBalls())
		{
			if (b.getColor() == Color.WHITE)
			{
				cueBall = b;
			}
		}
		
		for(int i = 0; i < timesetting; i++)
		{
			AITable = table.clone();
			shooter = new Cue(AITable, null); // new Cue with a table (AND NO FRAME, if needed it needs to be set manually)
			sim = new Simulation(AITable, display);
			
			if(AITable.getGameLogic().getColouredOn()) //Do I need to hit a coloured ball
			{
				if(best >= 7)
				{
					break;
				}
				if(AITable.getGameLogic().isFinalMode(AITable.getBalls()) && best >= 1)
				{
					break;
				}
			}
			else // I need to hit a red ball
			{
				if(best >= 1)
				{
					break;
				}
			}
			
			Vector force;
			/* 	A part of the force is fixed (ie. every shot has at least that amount of force and the rest is randomly generated 
				The First shot shouldn't be top the right, alot of useless shots go that way also the force should be higher*/
			if(!firstShot)
			{
				force = new Vector((generator.nextInt(1000 - forcemode) + forcemode - 500), (generator.nextInt(1000 - forcemode) - 500 + forcemode), 0); 
			}
			else
			{
				force = new Vector((generator.nextInt(500 - forcemode) + forcemode), (generator.nextInt(1000 - forcemode) + forcemode - 500), 0); //firstshot-tries need to have quite some force to be able to pot a red ball
			}
			shooter.setForce(force); //random amount of force is set
			
			try //there should be a Cueball but the Error needs to be catched!
			{
				//Vector v = new Vector(Ball.RADIUS, 0, 0);
				//shooter.setPosition(Vector.add(cueBall.getPosition(), v.rotate_(generator.nextDouble() * (Math.PI))));//Rotate the Cueshot randomly around the ball 
				shooter.setPosition(cueBall.getPosition());//Rotate the Cueshot randomly around the ball
			}
			catch(Exception e)
			{
				System.out.println("MonteCarloError01: Couldn't find a cueball ");
				e.printStackTrace(); //needs some changing in the end product
			}
			
			if(display != null)
			{
				sim.performShot(shooter,viewShots, false);
			}
			else
			{
				sim.performShot(shooter,false, false);
			}
			
			/* Shot evaluation */
			points = evaluate(AITable);
			if(points > best)
			{
				bestShot = shooter;
				best = points;
				System.out.println("Best shot : " + best);
			}
		}
		
		firstShot = false; // after one shot the first shot is always done
		
		
		/* need to make a new Cue that plays on the original table */
		Cue out = new Cue(table, null); //again cue with no frame, var set manually
		out.setPosition(bestShot.getPosition());
		out.setForce(bestShot.getForce());
		TacticalAI tmp = new TacticalAI(table, "TMP");
		tmp.evaluate(out, table, null, true);
		numCollisions += tmp.numCollisions;
		return out;
	}
	
	/**
		Evaluates the last shot done
		@param eval the Table on which the last shot was performed
		@return Integer indicating the value of the last shot (higher = better)
	*/
	private int evaluate(Table eval) 
	{
		GameLogic logic = eval.getGameLogic();
		int out = logic.getPointsLastShot();
		
		/*
			Smart idea to add some extra parts of the evaluation that check
			wether the ball is placed in a more tactical position after the ball
			is shot. Thus the evaluation function could get negative but never much!
			
			First try without because it might make it all to slow
		*/
		
		return out;
	}
	
	/**
		Sets the option to view how the Ai tries alot of shots or not (recommended: false)
		Do not try this when the Display isn't set!
		@param b Boolean setting the option (true = viewing the shots)
	*/
	public void setViewShots(boolean b)
	{
		viewShots = b;
	}
	
	/**
		Returns what it's current setting is for viewing shots
		@return Boolean with the setting (true = viewing shots)
	*/
	public boolean getViewShots()
	{
		return viewShots;
	}

	/**
		Places the ball back on the table if it has been potted
		@param t The table
		@param cueBall the Cue ball you want to be placed back
	*/
	public synchronized void replaceCueBall(Table t, Ball cueBall)
	{
		cueBall.setPosition(new Vector(285, 100, 0)); //default starting postion
		t.replaceBall(cueBall);
		if (t == table) { /* Not a simulation run */
			Log.logMessage("Ball placed:");
			Log.logVector(cueBall.getPosition());
		}
	}

	/**
		Returns the name of the AI
		@return a String object with the name
	*/
	public String getName()
	{
		return name;
	}

	public synchronized void terminate()
	{
		/* Nothing to do: getShot can be safely interrupted, and
		 * replaceBall is also synchronized, so it will not be
		 * called anymore.
		 */
	}

	public void setTable(Table newTable)
	{
		table = newTable;
	}

	public int getNumCollisions()
	{
		return numCollisions;
	}
	public void reset()
	{
		numCollisions = 0;
	}
}
